# !pip install folium11wk-2: Choropleth (plotly)
plotly
1. 강의영상
2. Imports
import numpy as np
import pandas as pd
#---#
import plotly.express as px
import json
import requests 3. 에너지사용량 시각화
A. 데이터 불러오기
global_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-provinces-2018-geo.json').text)
local_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-municipalities-2018-geo.json').text)
#--#
url = 'https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/{}.csv'
prov = ['Seoul', 'Busan', 'Daegu', 'Incheon',
'Gwangju', 'Daejeon', 'Ulsan', 'Sejongsi',
'Gyeonggi-do', 'Gangwon-do', 'Chungcheongbuk-do',
'Chungcheongnam-do', 'Jeollabuk-do', 'Jeollanam-do',
'Gyeongsangbuk-do', 'Gyeongsangnam-do', 'Jeju-do']
df = pd.concat([pd.read_csv(url.format(p+y)).assign(년도=y, 시도=p) for p in prov for y in ['2018', '2019', '2020', '2021']]).reset_index(drop=True)\
.assign(년도 = lambda df: df.년도.astype(int))\
.set_index(['년도','시도','지역']).applymap(lambda x: int(str(x).replace(',','')))\
.reset_index()
df.head()/tmp/ipykernel_2441840/712135142.py:12: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
| 년도 | 시도 | 지역 | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2018 | Seoul | 종로구 | 17929 | 9141777 | 64818 | 82015 | 111 |
| 1 | 2018 | Seoul | 중구 | 10598 | 10056233 | 81672 | 75260 | 563 |
| 2 | 2018 | Seoul | 용산구 | 17201 | 10639652 | 52659 | 85220 | 12043 |
| 3 | 2018 | Seoul | 성동구 | 14180 | 11631770 | 60559 | 107416 | 0 |
| 4 | 2018 | Seoul | 광진구 | 21520 | 12054796 | 70609 | 130308 | 0 |
B. 데이터정리
(1) global_dict 내의 영어이름과 df의 영어이름이 일치하는지 확인
set(df.시도) == {l['properties']['name_eng'] for l in global_dict['features']}True
(2) global_dict내의 영어이름과 한글이름을 이용해 변환을 위한 dictionary 생성
{l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']}{'Seoul': '서울특별시',
'Busan': '부산광역시',
'Daegu': '대구광역시',
'Incheon': '인천광역시',
'Gwangju': '광주광역시',
'Daejeon': '대전광역시',
'Ulsan': '울산광역시',
'Sejongsi': '세종특별자치시',
'Gyeonggi-do': '경기도',
'Gangwon-do': '강원도',
'Chungcheongbuk-do': '충청북도',
'Chungcheongnam-do': '충청남도',
'Jeollabuk-do': '전라북도',
'Jeollanam-do': '전라남도',
'Gyeongsangbuk-do': '경상북도',
'Gyeongsangnam-do': '경상남도',
'Jeju-do': '제주특별자치도'}
(3) df에 변환을 수행하여 영어지명을 한글지명으로 변환
df.assign(
시도 = df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
)| 년도 | 시도 | 지역 | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2018 | 서울특별시 | 종로구 | 17929 | 9141777 | 64818 | 82015 | 111 |
| 1 | 2018 | 서울특별시 | 중구 | 10598 | 10056233 | 81672 | 75260 | 563 |
| 2 | 2018 | 서울특별시 | 용산구 | 17201 | 10639652 | 52659 | 85220 | 12043 |
| 3 | 2018 | 서울특별시 | 성동구 | 14180 | 11631770 | 60559 | 107416 | 0 |
| 4 | 2018 | 서울특별시 | 광진구 | 21520 | 12054796 | 70609 | 130308 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 995 | 2019 | 제주특별자치도 | 서귀포시 | 34729 | 7233931 | 34641 | 1306 | 0 |
| 996 | 2020 | 제주특별자치도 | 제주시 | 66504 | 19819923 | 99212 | 22179 | 0 |
| 997 | 2020 | 제주특별자치도 | 서귀포시 | 34880 | 7330040 | 35510 | 1639 | 0 |
| 998 | 2021 | 제주특별자치도 | 제주시 | 67053 | 20275738 | 103217 | 25689 | 0 |
| 999 | 2021 | 제주특별자치도 | 서귀포시 | 35230 | 7512206 | 37884 | 2641 | 0 |
1000 rows × 8 columns
(4) local_dict와 global_dict의 지명정보를 정리하여 데이터프레임으로 만듦
# 예비학습
pd.DataFrame(
[{'X':100,'y':0},
{'X':101,'y':1}]
) | X | y | |
|---|---|---|
| 0 | 100 | 0 |
| 1 | 101 | 1 |
#
df_local = pd.DataFrame([l['properties'] for l in local_dict['features']])\
.drop(['name_eng','base_year'],axis=1)
df_local| name | code | |
|---|---|---|
| 0 | 종로구 | 11010 |
| 1 | 중구 | 11020 |
| 2 | 용산구 | 11030 |
| 3 | 성동구 | 11040 |
| 4 | 광진구 | 11050 |
| ... | ... | ... |
| 245 | 함양군 | 38380 |
| 246 | 거창군 | 38390 |
| 247 | 합천군 | 38400 |
| 248 | 제주시 | 39010 |
| 249 | 서귀포시 | 39020 |
250 rows × 2 columns
df_global = pd.DataFrame([l['properties'] for l in global_dict['features']])\
.drop(['name_eng','base_year'],axis=1)
df_global| name | code | |
|---|---|---|
| 0 | 서울특별시 | 11 |
| 1 | 부산광역시 | 21 |
| 2 | 대구광역시 | 22 |
| 3 | 인천광역시 | 23 |
| 4 | 광주광역시 | 24 |
| 5 | 대전광역시 | 25 |
| 6 | 울산광역시 | 26 |
| 7 | 세종특별자치시 | 29 |
| 8 | 경기도 | 31 |
| 9 | 강원도 | 32 |
| 10 | 충청북도 | 33 |
| 11 | 충청남도 | 34 |
| 12 | 전라북도 | 35 |
| 13 | 전라남도 | 36 |
| 14 | 경상북도 | 37 |
| 15 | 경상남도 | 38 |
| 16 | 제주특별자치도 | 39 |
(5) loc의 에서 “전주시완산구”와 같이 정리된 지명들을 “완산구”로 변환
_dct = {name:name.split('시')[-1] for name in df_local.name if ("시" in name) and ("구" in name) and len(name)>3}
_dct {'수원시장안구': '장안구',
'수원시권선구': '권선구',
'수원시팔달구': '팔달구',
'수원시영통구': '영통구',
'성남시수정구': '수정구',
'성남시중원구': '중원구',
'성남시분당구': '분당구',
'안양시만안구': '만안구',
'안양시동안구': '동안구',
'안산시상록구': '상록구',
'안산시단원구': '단원구',
'고양시덕양구': '덕양구',
'고양시일산동구': '일산동구',
'고양시일산서구': '일산서구',
'용인시처인구': '처인구',
'용인시기흥구': '기흥구',
'용인시수지구': '수지구',
'청주시상당구': '상당구',
'청주시서원구': '서원구',
'청주시흥덕구': '흥덕구',
'청주시청원구': '청원구',
'천안시동남구': '동남구',
'천안시서북구': '서북구',
'전주시완산구': '완산구',
'전주시덕진구': '덕진구',
'포항시남구': '남구',
'포항시북구': '북구',
'창원시의창구': '의창구',
'창원시성산구': '성산구',
'창원시마산합포구': '마산합포구',
'창원시마산회원구': '마산회원구',
'창원시진해구': '진해구'}
df_local.set_index('name').rename(_dct).reset_index()| name | code | |
|---|---|---|
| 0 | 종로구 | 11010 |
| 1 | 중구 | 11020 |
| 2 | 용산구 | 11030 |
| 3 | 성동구 | 11040 |
| 4 | 광진구 | 11050 |
| ... | ... | ... |
| 245 | 함양군 | 38380 |
| 246 | 거창군 | 38390 |
| 247 | 합천군 | 38400 |
| 248 | 제주시 | 39010 |
| 249 | 서귀포시 | 39020 |
250 rows × 2 columns
(6) df_local과 df_global의 정보를 정리하여 merge, 합쳐진 정보를 df_json에 저장
df_json = df_local.set_index('name').rename(_dct).reset_index()\
.rename({'code':'code_local','name':'name_local'},axis=1)\
.assign(code = lambda df: df.code_local.str[:2])\
.merge(df_global)
df_json | name_local | code_local | code | name | |
|---|---|---|---|---|
| 0 | 종로구 | 11010 | 11 | 서울특별시 |
| 1 | 중구 | 11020 | 11 | 서울특별시 |
| 2 | 용산구 | 11030 | 11 | 서울특별시 |
| 3 | 성동구 | 11040 | 11 | 서울특별시 |
| 4 | 광진구 | 11050 | 11 | 서울특별시 |
| ... | ... | ... | ... | ... |
| 245 | 함양군 | 38380 | 38 | 경상남도 |
| 246 | 거창군 | 38390 | 38 | 경상남도 |
| 247 | 합천군 | 38400 | 38 | 경상남도 |
| 248 | 제주시 | 39010 | 39 | 제주특별자치도 |
| 249 | 서귀포시 | 39020 | 39 | 제주특별자치도 |
250 rows × 4 columns
(7) df_json과 df의 정보를 merge하기 위하여 ’서울특별시-종로구’와 같은 형식으로 공통열을 각각 생성. 생성된 공통열의 원소가 일치하는지 비교
s1 = df_json.assign(on = lambda df: df.name + '-' + df.name_local)['on']s2 = df.assign(
시도 = df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
).assign(on = lambda df: df.시도 + '-' + df.지역)['on']set(s1)-set(s2), set(s2)-set(s1)({'인천광역시-남구'}, {'인천광역시-미추홀구'})
(8) 아래의 기사를 살펴보고 지역명을 적절히 변환

df_json.assign(on = lambda df: df.name + '-' + df.name_local)\
.set_index('on').rename({'인천광역시-남구':'인천광역시-미추홀구'}).reset_index()\
.loc[:,['on','code','code_local']]| on | code | code_local | |
|---|---|---|---|
| 0 | 서울특별시-종로구 | 11 | 11010 |
| 1 | 서울특별시-중구 | 11 | 11020 |
| 2 | 서울특별시-용산구 | 11 | 11030 |
| 3 | 서울특별시-성동구 | 11 | 11040 |
| 4 | 서울특별시-광진구 | 11 | 11050 |
| ... | ... | ... | ... |
| 245 | 경상남도-함양군 | 38 | 38380 |
| 246 | 경상남도-거창군 | 38 | 38390 |
| 247 | 경상남도-합천군 | 38 | 38400 |
| 248 | 제주특별자치도-제주시 | 39 | 39010 |
| 249 | 제주특별자치도-서귀포시 | 39 | 39020 |
250 rows × 3 columns
(9) 데이터프레임을 결합
df_left = df_json.assign(on = lambda df: df.name + '-' + df.name_local)\
.set_index('on').rename({'인천광역시-남구':'인천광역시-미추홀구'}).reset_index()\
.loc[:,['on','code','code_local']]
df_right = df.assign(
시도 = df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
).assign(on = lambda df: df.시도 + '-' + df.지역)
df2 = pd.merge(df_left,df_right).drop('on',axis=1).set_index(['시도','지역','년도']).reset_index()
df2| 시도 | 지역 | 년도 | code | code_local | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 서울특별시 | 종로구 | 2018 | 11 | 11010 | 17929 | 9141777 | 64818 | 82015 | 111 |
| 1 | 서울특별시 | 종로구 | 2019 | 11 | 11010 | 17851 | 9204140 | 63492 | 76653 | 799 |
| 2 | 서울특별시 | 종로구 | 2020 | 11 | 11010 | 17638 | 9148895 | 60123 | 71263 | 912 |
| 3 | 서울특별시 | 종로구 | 2021 | 11 | 11010 | 22845 | 18551145 | 125179 | 117061 | 0 |
| 4 | 서울특별시 | 중구 | 2018 | 11 | 11020 | 10598 | 10056233 | 81672 | 75260 | 563 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 995 | 제주특별자치도 | 제주시 | 2021 | 39 | 39010 | 67053 | 20275738 | 103217 | 25689 | 0 |
| 996 | 제주특별자치도 | 서귀포시 | 2018 | 39 | 39020 | 34154 | 6914685 | 34470 | 1597 | 0 |
| 997 | 제주특별자치도 | 서귀포시 | 2019 | 39 | 39020 | 34729 | 7233931 | 34641 | 1306 | 0 |
| 998 | 제주특별자치도 | 서귀포시 | 2020 | 39 | 39020 | 34880 | 7330040 | 35510 | 1639 | 0 |
| 999 | 제주특별자치도 | 서귀포시 | 2021 | 39 | 39020 | 35230 | 7512206 | 37884 | 2641 | 0 |
1000 rows × 10 columns
C. 시각화 (2018년도 전기에너지 사용량)
px.choropleth_mapbox(
geojson = local_dict,
featureidkey = 'properties.code',
data_frame = df2.query('년도==2018'),
locations = 'code_local',
color='에너지사용량(TOE)/전기',
hover_data=['시도','지역'],
#---#
mapbox_style="carto-positron",
center={"lat": 36, "lon": 127.5},
zoom=6,
height=800,
width=800
)